﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MMOEng;
using System.Net.Sockets;
using MMOShared.Packets;
using System.Runtime.Serialization.Formatters.Binary;
using MMOShared;
using System.Windows.Forms;
using System.Threading;

namespace MMOConnector
{
    public class UpdateDownloader
    {
        private TcpClient client;
        private NetworkStream stream;
        private BinaryFormatter bf;
        private GameConfiguration gc;
        private VersionsAvailable versions;

        public delegate void StatusUpdated(string status);
        public delegate void HeaderReceived(UpdateHeader head);
        public delegate void StatusLabled();
        public delegate void PieceDownloaded(string file, int piece);
        public delegate void FileDownloaded(string status);
        public delegate void VersionFinished(string status);
        public delegate void DownloadFinished();

        public event StatusUpdated OnStatusUpdated;
        public event HeaderReceived OnHeaderReceived;
        public event StatusLabled OnStatusLabeled;
        public event PieceDownloaded OnPieceDownloaded;
        public event FileDownloaded OnFileDownloaded;
        public event DownloadFinished OnDownloadFinished;
        public event VersionFinished OnVersionFinished;

        public UpdateDownloader(GameConfiguration gc, TcpClient client, VersionsAvailable versions)
        {
            this.client = client;
            this.stream = client.GetStream();
            this.bf = new BinaryFormatter();
            this.gc = gc;
            this.versions = versions;
        }

        public void DownloadAllUpdates(){
            //Download all the versions we need
            for (int i = 0; i < versions.AllVersions.Length; i++)
            {
                if (versions.AllVersions[i] > gc.GameVersion)
                { 
                    UInt16 ver = versions.AllVersions[i];

                    OnVersionFinished("Downloading update "+ver+" of "+versions.LatestVersion);

                    AskForUpdateData(ver);
                    OnStatusUpdated("Requesting version " + ver + " from server...");

                    UpdateHeader revision = WaitForUpdateHeader();
                    OnHeaderReceived(revision);

                    ResourceManager resMan = WaitForResourceManager(ver);

                    OnStatusLabeled();
                    Dictionary<string, FileUpdatePiece[]> pieces = DownloadVersion(ver, resMan, revision);

                    foreach (string resId in pieces.Keys)
                    {
                        FileUpdatePiece[] thisFile = pieces[resId];
                        List<FileUpdatePiece> nL = new List<FileUpdatePiece>(thisFile);
                        string filePath = resMan.Lookup[resId];
                        //TEMP FOR DEBUGGING
                        FileUpdater.VerifySuccessfulUpdate(nL, "Dwld_Tmp\\" + filePath, "Test\\" + ver + "\\" + filePath);
                        //UNCOMMENT THIS LINE WHEN WE ARE SURE THAT UPDATING WORKS
                        FileUpdater.UpdateFileFromList(nL, "Dwld_Tmp\\" + filePath);
                    }
                    OnStatusUpdated("Verifying downloaded files...");
                    Thread.Sleep(2000);
                }
            }
            OnDownloadFinished();
        }

        private Dictionary<string, FileUpdatePiece[]> DownloadVersion(UInt16 version, ResourceManager man, UpdateHeader rev)
        {
            //stats
            int piecesDownloaded = 0;
            int filesDownloaded = 0;

            //keep track of files and pieces
            Dictionary<string, FileUpdatePiece[]> versionPieces = new Dictionary<string, FileUpdatePiece[]>();
            List<FileUpdatePiece> currentFile = new List<FileUpdatePiece>();

            //remember the name of the file in the previous piece
            string lastResID = null;

            while (piecesDownloaded < rev.NumberOfPieces)
            {
                if (client.Connected && stream.DataAvailable)
                {
                    object rec = bf.Deserialize(stream);
                    if (rec is FileUpdatePiece)
                    {
                        //downloaded a piece
                        FileUpdatePiece p = (FileUpdatePiece)rec;

                        //update labels and counters
                        OnPieceDownloaded(man.Lookup[p.ResourceID],++piecesDownloaded);

                        //check if this piece is the first piece of a new file
                        if (lastResID != p.ResourceID && lastResID != null)
                        {
                            //starting a new file, store the pieces for the last file in the dictionary by the
                            //previous piece's resource id.
                            versionPieces.Add(lastResID, currentFile.ToArray());
                            //clear out the list of pieces for the new file
                            currentFile.Clear();
                            //finally, add the first piece of the new file to the list of it's pieces
                            currentFile.Add(p);
                            //update counters and labels
                            OnFileDownloaded((++filesDownloaded) + " / " + rev.NumberOfFiles);
                        }
                        else
                        {
                            //this piece is another piece of the same file, so just add it to the current file's list
                            currentFile.Add(p);
                        }
                        //prepare for next file piece
                        lastResID = p.ResourceID;
                    }
                }
            }
            //Finally add the last file
            versionPieces.Add(lastResID, currentFile.ToArray());
            OnFileDownloaded((++filesDownloaded) + " / " + rev.NumberOfFiles);

            return versionPieces;
        }

        private UpdateHeader WaitForUpdateHeader()
        {
            bool waitingForUpdateHeader = true;
            UpdateHeader head = null;
            while (waitingForUpdateHeader)
            {
                if (client.Connected && stream.DataAvailable)
                {
                    object rec = bf.Deserialize(stream);
                    if (rec is UpdateHeader)
                    {
                        head = (UpdateHeader)rec;
                        waitingForUpdateHeader = false;
                    }
                    else
                    {
                        MessageBox.Show("Error: Didn't receive update header, received " + rec.ToString() + " instead.");
                    }
                }
            }
            return head;
        }

        private void AskForUpdateData(UInt16 version)
        {
            UpdateRequest req = new UpdateRequest(version, UpdateRequest.Action.DOWNLOAD_REQUEST);
            bf.Serialize(stream, req);
            stream.Flush();
        }

        private ResourceManager WaitForResourceManager(UInt16 version)
        {
            bool waitingForResourceManager = true;
            ResourceManager resMan = null;
            while (waitingForResourceManager)
            {
                if (client.Connected && stream.DataAvailable)
                {
                    object rec = bf.Deserialize(stream);
                    if (rec is ResourceManager)
                    {
                        resMan = (ResourceManager)rec;
                        waitingForResourceManager = false;
                    }
                    else
                    {
                        MessageBox.Show("Error: Didn't receive resource manager, received " + rec.ToString() + " instead.");
                    }
                }
            }
            if (resMan.Version != version)
            {
                MessageBox.Show("Error: Received unexpected version. Was expecting version " + version + ", but got version " + resMan.Version + " instead.");
            }
            return resMan;
        }
    }
}
